/**
 * WTF Engine
 *
 * License... etc.
 **
 * Scripting Support
 *
 * Features:
 * 	- Python bindings to various aspects of the engine
 **
 * Authors: Sean Micklethwaite
 **/

#ifndef __scriptingH__
#define __scriptingH__

#include "kernel.h"
#include "wtftypes.h"
#include "physics.h"
#include "dator.h"
#include "loggable.h"


/**
 * Adaptor so boost can use our GcReferences
 **/
namespace WtfEngine{
	template <class T> T* get_pointer(const WtfEngine::GcReference<T>& p) {
		return &*p;
	};
	template <class T> T* get_pointer(const WtfEngine::WeakRef<T>& p) {
		return &*p;
	};
	template <class T> T* get_pointer(const WtfEngine::CacheRef<T>& p) {
		return &*p;
	};
	template <class T> T* get_pointer(const WtfEngine::PolymorphicCacheRef<T>& p) {
		return &*p;
	};
};

#ifdef SCRIPTING_LUA
#	define LUA_DIR	"lua5.1/"
#	include <lua5.1/lua.hpp>
#	define LUA_INCLUDE_HPP_INCLUDED
#	include <luabind/luabind.hpp>
#else
//#	define BOOST_PYTHON_STATIC_LIB	1
#	include <boost/python.hpp>
#	include <boost/python/suite/indexing/map_indexing_suite.hpp>
#	include <boost/python/suite/indexing/vector_indexing_suite.hpp>
#	include "list_indexing_suite.h"
#endif

namespace boost {
	// Force boost to treat our reference types as value types.
	template <class T> struct is_class<WtfEngine::GcReference<T> >: public is_class<int> {};
	template <class T> struct is_class<WtfEngine::WeakRef<T> >: public is_class<int> {};
	template <class T> struct is_class<WtfEngine::CacheRef<T> >: public is_class<int> {};
	template <class T> struct is_class<WtfEngine::PolymorphicCacheRef<T> >: public is_class<int> {};

	namespace python {
		using boost::get_pointer;
		
		template <class T> struct pointee<WtfEngine::GcReference<T> >
		{
			typedef T type;
		};
		template <class T> struct pointee<WtfEngine::WeakRef<T> >
		{
			typedef T type;
		};
		template <class T> struct pointee<WtfEngine::CacheRef<T> >
		{
			typedef T type;
		};
		template <class T> struct pointee<WtfEngine::PolymorphicCacheRef<T> >
		{
			typedef T type;
		};
	}
}

namespace WtfEngine
{
	/** 
	 * Singleton Body which hosts the scripting session
	 */
	class ScriptHost : public Singleton<ScriptHost>, public Loggable<ScriptHost>
	{
	private:
#ifdef SCRIPTING_LUA
		lua_State*	mpState;
#endif

	public:
		ScriptHost();
		~ScriptHost();

		void LoadScript(tString sFile);
		void EvalScript(const String& sScript);

	public:
#ifdef SCRIPTING_LUA
		void RegisterHook(tString sName, lua_CFunction pHook){
			lua_register(mpState, sName, pHook);
		};
#endif

		/*** Base API Methods ***/
		
		/// Prints to system log
		static void _DebugPrint(const String& s);
		/// Logs to developer's console
		static void _ConsolePrint(const String& s);
	};

	template <class T>
	struct ScriptCacheRef {
		static typename T::tRef Create(const String& sName) {
			return typename T::tCacheRef(sName);
		};
	};
#	define DECL_CACHE_REF(sName, T, PT) \
		def(sName, &ScriptCacheRef<T>::Create); \
		implicitly_convertible<T::tRef, T::tCacheRef>(); \
		implicitly_convertible<T::tCacheRef, PT::tCacheRef>();


	class ScriptFunctorWrapper : public IFunctor<>, public GcObject, public boost::python::wrapper<Functor>
	{
		void operator ()(){
			try{
				this->get_override("Invoke")();
			}catch(const boost::python::error_already_set & e){
				//PyErr_Print();
			};
		};

		/// Required comparison operators
		inline bool operator == (const ScriptFunctorWrapper& f) const {
			return this->get_override("Invoke") == f.get_override("Invoke");
		};
		inline bool operator == (const IFunctor& f) const {
			ScriptFunctorWrapper const * pF = dynamic_cast<ScriptFunctorWrapper const *>(&f);
			return (pF != NULL && *this == *pF);
		};
	};
	
	class ScriptFunctionWrapper: public IFunctor<>, public GcObject {
		boost::python::object	mFunc;
		bool mbTakesEvent;

	public:
		ScriptFunctionWrapper(const boost::python::object& f, bool bTakesEvent = false):
		  Functor(), mFunc(f), mbTakesEvent(bTakesEvent) {
		};

		void operator ()(){
			try{
				if(mbTakesEvent) {
					assert(Kernel::GetInstance().GetEventStatus() != Event::NONE);
					mFunc(boost::python::ptr(Kernel::GetInstance().GetCurrentEvent<Event>()));
				} else {
					mFunc();
				};
			}catch(const boost::python::error_already_set & e){
				PyErr_Print();
			};
		};

		/// Required comparison operators
		inline bool operator == (const ScriptFunctionWrapper& f) const {
			return (mFunc == f.mFunc);
		};
		inline bool operator == (const IFunctor& f) const {
			ScriptFunctionWrapper const * pF = dynamic_cast<ScriptFunctionWrapper const *>(&f);
			return (pF != NULL && *this == *pF);
		};

		GC_AUTOSIZE(ScriptFunctionWrapper);
	};

	class ScriptEventHandler : public EventHandler
	{
	public:
		ScriptEventHandler(const boost::python::object & f)
			: EventHandler(new ScriptFunctionWrapper(f, true)) {};

		ScriptEventHandler(const boost::python::object & f, tPriority prio)
			: EventHandler(new ScriptFunctionWrapper(f, true), prio) {};
	};
	

	/**
	 * Gives access to settings from script. Memory for this object is managed
	 * by Python.
	 **/
	template <typename tSetting>
	class ScriptSettingWrapper {
		typename tSetting::tRef	mrSetting;
		
		public:
			ScriptSettingWrapper(const String& sName)
			: mrSetting(tSetting::Get(sName)) {};

			explicit ScriptSettingWrapper(tSetting * pSetting)
			: mrSetting(pSetting) {
			};
			
			/// Conversion to normal setting reference
			operator typename const tSetting::tRef& () const {
				return mrSetting;
			};

			template <class U>
			U Get() const {
				return (U) mrSetting->Value;
			};

			/// Setting assignment
			template <class U>
			void operator = (const U& v) {
				(*mrSetting) = v;
			};

			template <class U>
			static ScriptSettingWrapper Set(const String& sName, const U& val) {
				return ScriptSettingWrapper(tSetting::Set(sName, val));
			};
	};
	


	/**** Set Wrapper ****/

	// Forward declaration
    template <class Container, bool NoProxy, class DerivedPolicies>
    class set_indexing_suite;
    
    namespace detail
    {
        template <class Container, bool NoProxy>
        class final_set_derived_policies 
            : public set_indexing_suite<Container, 
                NoProxy, final_set_derived_policies<Container, NoProxy> > {};
    }

    template <
        class Container, 
        bool NoProxy = false,
        class DerivedPolicies 
            = detail::final_set_derived_policies<Container, NoProxy> >
    class set_indexing_suite 
		: public boost::python::indexing_suite<Container, DerivedPolicies, NoProxy, true, typename Container::value_type, typename Container::iterator>
    {
    public:
        typedef typename Container::value_type data_type;
        typedef typename Container::value_type key_type;
        typedef typename Container::iterator index_type;
        typedef typename Container::size_type size_type;
        typedef typename Container::difference_type difference_type;
        
        template <class Class>
        static void 
        extension_def(Class& cl)
        {
            cl
                .def("append", &base_append)
                .def("extend", &base_extend)
            ;
        }
        
        static 
		typename boost::mpl::if_<
			boost::is_class<data_type>
          , data_type&
          , data_type
        >::type
        get_item(Container& container, index_type i)
        { 
            return *i;
        }

		static boost::python::object 
        get_slice(Container& container, index_type from, index_type to)
        { 
            if (from > to)
                return boost::python::object(Container());
            return boost::python::object(Container(container.begin()+from, container.begin()+to));
        }

        static void 
        set_item(Container& container, index_type i, data_type const& v)
        { 
            container.insert(v);
        }

        static void 
        set_slice(Container& container, index_type from, 
            index_type to, data_type const& v)
        { 
            if (from > to) {
                return;
            }
            else {
                container.erase(from, to);
                container.insert(from, v);
            }
        }

        template <class Iter>
        static void 
        set_slice(Container& container, index_type from, 
            index_type to, Iter first, Iter last)
        { 
            if (from > to) {
                container.insert(from, first, last);
            }
            else {
                container.erase(from, container.begin()+to);
                container.insert(from, first, last);
            }
        }

        static void 
        delete_item(Container& container, index_type i)
        { 
            container.erase(i);
        }
        
        static void 
        delete_slice(Container& container, index_type from, index_type to)
        { 
            if (from > to) {
                // A null-op.
                return;
            }
            container.erase(container.begin()+from, container.begin()+to);
        }
        
        static size_t
        size(Container& container)
        {
            return container.size();
        }
        
        static bool
        contains(Container& container, key_type const& key)
        {
            return container.find(key) != container.end();
        }
        
        static index_type
        get_min_index(Container& container)
        { 
            return container.begin();
        }

        static index_type
        get_max_index(Container& container)
        { 
            return container.end();
        }
      
        static bool 
        compare_index(Container& /*container*/, index_type a, index_type b)
        {
            return a != b;
        }
        
        static index_type
        convert_index(Container& container, PyObject* i_)
        {
			// Using iterators as index - has no python equivalent
            PyErr_SetString(PyExc_TypeError, "Invalid index type");
            boost::python::throw_error_already_set();
            return index_type();
        }
      
        static void 
        append(Container& container, data_type const& v)
        { 
            container.insert(v);
        }
        
        template <class Iter>
        static void 
        extend(Container& container, Iter first, Iter last)
        { 
            container.insert(first, last);
        }
        
    private:
    
        static void
        base_append(Container& container, boost::python::object v)
        {
            boost::python::extract<data_type&> elem(v);
            // try if elem is an exact Data
            if (elem.check())
            {
                DerivedPolicies::append(container, elem());
            }
            else
            {
                //  try to convert elem to data_type
                boost::python::extract<data_type> elem(v);
                if (elem.check())
                {
                    DerivedPolicies::append(container, elem());
                }
                else
                {
                    PyErr_SetString(PyExc_TypeError, 
                        "Attempting to append an invalid type");
                    boost::python::throw_error_already_set();
                }
            }
        }
        
        static void
        base_extend(Container& container, boost::python::object v)
        {
            std::vector<data_type> temp;
            boost::python::container_utils::extend_container(temp, v);
            DerivedPolicies::extend(container, temp.begin(), temp.end());
        }
    };
};

namespace Meta {
	/// Fetching real value from a Setting wrapper (real value is a reference to
	/// the setting)
	template <class tSetting>
	struct Value<WtfEngine::ScriptSettingWrapper<tSetting> > {
		typedef typename tSetting::tRef tValue;

		inline static typename ValueTraits<tValue>::tConstRef get(typename ValueTraits<WtfEngine::ScriptSettingWrapper<tSetting> >::tConstRef t) {
			return (typename ValueTraits<tValue>::tConstRef)t;
		};
	};
};

#endif
